﻿using System.Collections.Generic;
using System.IO;
using TMPro;
using UnityEngine;
using UnityEngine.Video;

namespace VCity
{

    public class AdsBoard : MonoBehaviour
    {
        public enum AutoScaler
        {
            None,
            Horizontal,
            Vertical,
            // stay in limit area
            Auto
        }

        [System.Serializable]
        public struct TextItem
        {
            public int i;
            public TextMeshPro tmp;
            //public AutoScaler autoScaler;
        }

        [System.Serializable]
        public struct TextureItem
        {
            [Header("Data Index")]
            public int i;

            [Space]
            public MeshRenderer render;
            public string textureId;

            [Space]
            public AutoScaler autoScaler;
            public bool hideNoData;
        }

        [System.Serializable]
        public struct VideoItem
        {
            //public int i;
            public VideoPlayer player;
            public AutoScaler autoScaler;

            // runtime
            // Audio settings, for webgl
            //internal float minDistance;
            //internal float maxDistance;
            internal float audioF0, audioF1;
        }

        //public TextMeshPro text;
        public TextItem[] texts;

        [Space()]
        public TextureItem[] textures;

        [Space()]
        public VideoItem[] videoPlayers;

        [Header("Config")]
        public bool playVideoOnSetData;
        // video player

        [HideInInspector]
        public AdsData data;

        // Lazy init
        bool initedMaterial;
        Dictionary<VideoPlayer, int> videoPlayersDic;

        #region Unity Methods
        private void Awake()
        {
            // video
            VideoPlayer.EventHandler c = OnVideoPlayerPrepareCompleted;
            videoPlayersDic = new Dictionary<VideoPlayer, int>(videoPlayers.Length);
            for (int i = 0, length = videoPlayers.Length; i < length; i++)
            {
                var item = videoPlayers[i];
                var player = videoPlayers[i].player;

                if (player.gameObject.activeSelf)
                {
                    // prepared event handler
                    player.prepareCompleted += c;
                    videoPlayersDic[player] = i;

                    // audio for webgl
                    InitVideoPlayerAudioGL(ref item);
                    videoPlayers[i] = item;
                }
            }
        }
        #endregion

        #region Update Data
        public void SetData(AdsData data)
        {
            this.data = data;
            var res = GameResources.Instance;

            for (int i = 0, length = texts.Length; i < length; i++)
            {
                var item = texts[i];
                if (IsActive(item.tmp))
                {
                    string s = data.GetText(item.i);
                    item.tmp.text = data.GetText(item.i);
                    item.tmp.enabled = !string.IsNullOrEmpty(s);
                }
            }

            for (int i = 0, length = textures.Length; i < length; i++)
            {
                var item = textures[i];

                if (IsActive(item.render))
                {
                    res.textureDic.TryGetValue(data.texture, out var texture);
                    SetTexture(item, texture);
                }
            }

            for (int i = 0, length = videoPlayers.Length; i < length; i++)
            {
                var item = videoPlayers[i];
                if (IsActive(item.player))
                {
                    // hide
                    if (!data.HasVideo)
                        item.player.gameObject.SetActive(false);
                    else
                    {
                        // set url but not play
                        if (playVideoOnSetData)
                            PlayVideoFromData(item);
                        else
                            SetVideo(item);
                    }
                }
            }
        }

        void SetTexture(in TextureItem texItem, Texture2D texture)
        {
            // hide
            var render = texItem.render;
            if (texItem.hideNoData)
                render.gameObject.SetActive(texture != null);
            if (texture == null)
                return;

            // init material
            // no need
            //if (!initedMaterial)
            //{
            //    initedMaterial = true;

            //    for (int i = 0, length = textures.Length; i < length; i++)
            //    {
            //        var item = textures[i];
            //        item.render.material = new Material(item.render.material);
            //    }
            //}

            // set main texture
            if (string.IsNullOrEmpty(texItem.textureId))
                render.material.mainTexture = texture;
            else
                render.material.SetTexture(texItem.textureId, texture);

            // auto scale
            if (texture != null)
            {
                AutoScale(render.gameObject.transform, texture.width, texture.height, texItem.autoScaler);
            }
        }

        private void OnVideoPlayerPrepareCompleted(VideoPlayer source)
        {
            // find 
            if (videoPlayersDic.TryGetValue(source, out int index))
            {
                AutoScale(source.gameObject.transform, source.width, source.height, videoPlayers[index].autoScaler);
                source.Play();
            }
        }

        void AutoScale(Transform tr, float width, float height, AutoScaler scaler)
        {
            if (scaler != AutoScaler.None)
            {
                float aspect = (float)width / height;
                Vector3 scale = tr.localScale;
                float originalAspect = scale.x / scale.y;

                switch (scaler)
                {
                    case AutoScaler.Horizontal:
                        scale.x = aspect * scale.y;
                        break;
                    case AutoScaler.Vertical:
                        scale.y = scale.x / aspect;
                        break;
                    case AutoScaler.Auto:
                        if (aspect > originalAspect)
                        {
                            scale.y = scale.x / aspect;
                        }
                        else if(aspect < originalAspect)
                        {
                            scale.x = aspect * scale.y;
                        }
                        break;
                }

                tr.localScale = scale;
            }
        }

        /// <summary>
        /// Play or stop play video
        /// </summary>
        /// <param name="play"></param>
        public void PlayVideo(bool play)
        {
            bool hasVideoData = data.HasVideo;
            if (play && hasVideoData)
            {
                for (int i = 0, length = videoPlayers.Length; i < length; i++)
                {
                    var v = videoPlayers[i];
                    PlayVideoFromData(v);
                }
            }
            else
            {
                for (int i = 0, length = videoPlayers.Length; i < length; i++)
                    videoPlayers[i].player.Stop();
            }
        }

        void SetVideo(VideoItem item)
        {
            string videoUrl = data.video.StartsWith("http") ? data.video : Path.Combine(Application.streamingAssetsPath, data.video);
            item.player.url = videoUrl;
        }

        void PlayVideoFromData(VideoItem item)
        {
            SetVideo(item);
            item.player.Prepare();
        }
        #endregion

        #region Webgl Audio
        void InitVideoPlayerAudioGL(ref VideoItem item)
        {
            var player = item.player;
            // audio for webgl
            player.audioOutputMode = VideoAudioOutputMode.Direct;
            var audioSource = player.GetComponent<AudioSource>();
            float m = audioSource.minDistance;
            float n = audioSource.maxDistance;
            m *= m;
            n *= n;
            //m = n * 0.75f;
            item.audioF0 = 1 / (m - n);
            item.audioF1 = -n / (m - n);

            audioSource.enabled = false;
        }

        public void UpdateVideoPlayerAudioGL(Vector3 charPos)
        {
            for (int i = 0, length = videoPlayers.Length; i < length; i++)
            {
                var item = videoPlayers[i];
                var player = item.player;
                if (player.gameObject.activeSelf)
                {
                    // distance
                    float magSqr = Vector3.SqrMagnitude(charPos - player.transform.position);
                    float vol = magSqr * item.audioF0 + item.audioF1;
                    player.SetDirectAudioVolume(0, Mathf.Clamp01(vol));
                    player.SetDirectAudioMute(0, vol == 0);
                }
            }
        }
        #endregion

        #region Interactive
        public void OnClick()
        {
            if (data.HasLink)
                Application.OpenURL(data.link);
        }
        #endregion

        static bool IsActive(Component comp)
        {
            return comp != null && comp.gameObject.activeSelf;
        }
    }
}